/*
 * Copyright (c) 2016 4PX Information Technology Co.,Ltd. All rights reserved.
 */
package com.open.spring.utils.soap;

import java.io.IOException;
import java.net.Proxy;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.TransformerException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.SoapHeader;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.soap.client.SoapFaultClientException;

import com.open.spring.utils.soap.header.SaajSoapMessageFactoryEx;
import com.open.spring.utils.soap.header.Security;
import com.open.spring.utils.soap.proxy.WebServiceTemplateEx;
import com.open.tools.StringUtils;
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;

/**
 * @author hcx
 * @date 2017年6月12日
 */
public class SoapUtil {
    public static String SOAP_TYPE_1_1 = SOAPConstants.SOAP_1_1_PROTOCOL;
    public static String SOAP_TYPE_1_2 = SOAPConstants.SOAP_1_2_PROTOCOL;
    public final static String wss4j_utility = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
    public final static String wss4j_secext = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    private static Logger logger = LoggerFactory.getLogger(SoapUtil.class);

    public static <T> T marshalSendAndReceive(String uri, Object requestObj, T responseObj, String soapType,
            List<SOAPHeaderElement> soapHeaderElements) {
        return marshalSendAndReceive(uri, requestObj, responseObj, soapType, soapHeaderElements, "");
    }

    /**
     * webservice的soap请求
     *
     * @param uri 请求对方soap的URL,例如：http://yodel.myshippingsystem.com/api/abolapi.asmx
     * @param requestObj 求请参数的对象
     * @param responseObj 响应参数的对象，注意是对象。
     * @param soapType soap类型，例如SOAP 1.2 Protocol;
     * @param soapHeaderElements 可以为null
     * @return T 响应参数的对象，与传入的响应对象responseObj不是同一个。
     */
    public static <T> T marshalSendAndReceive(String uri, Object requestObj, T responseObj, String soapType,
            List<SOAPHeaderElement> soapHeaderElements, String soapAction) {

        if (soapAction == null) {
            soapAction = "";
        }

        Date startTime = new Date();
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setClassesToBeBound(requestObj.getClass());

        Jaxb2Marshaller jaxb2UmMarshaller = new Jaxb2Marshaller();
        jaxb2UmMarshaller.setClassesToBeBound(responseObj.getClass());
        MessageFactory msgFactory = null;
        SOAPMessage message = null;
        try {
            msgFactory = MessageFactory.newInstance(soapType);
            // 加入头部信息
            if (null != soapHeaderElements && !soapHeaderElements.isEmpty()) {
                message = msgFactory.createMessage();
                SOAPPart soapPart = message.getSOAPPart();
                SOAPEnvelope envelope = soapPart.getEnvelope();
                SOAPHeader header = envelope.getHeader();
                if (header == null) {
                    header = envelope.addHeader();
                }
                for (SOAPHeaderElement sOAPHeaderElement : soapHeaderElements) {
                    header.addChildElement(sOAPHeaderElement);
                }
            }

        } catch (SOAPException e1) {
            logger.error("MessageFactory 初始化出错", e1);
            throw new RuntimeException(e1);

        }

        WebServiceTemplate webServiceTemplate = new WebServiceTemplate(jaxb2Marshaller, jaxb2UmMarshaller);
        // webServiceTemplate.setFaultMessageResolver(new SoapFaultResolver());
        SaajSoapMessageFactoryEx newSoapMessageFactory = new SaajSoapMessageFactoryEx(msgFactory);
        newSoapMessageFactory.setSoapMessage(message);
        webServiceTemplate.setMessageFactory(newSoapMessageFactory);

        webServiceTemplate.setDefaultUri(uri);

        final String finalSoapAction = soapAction;
        Object result = null;
        try {
            @SuppressWarnings("unchecked")
            T resultObje = (T) webServiceTemplate.marshalSendAndReceive(uri, requestObj,
                    new WebServiceMessageCallback() {
                        @Override
                        public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
                            ((SoapMessage) message).setSoapAction(finalSoapAction);
                        }
                    });
            result = resultObje;
            return resultObje;
        } catch (SoapFaultClientException sfce) {
            // QName faultCode = sfce.getFaultCode();
            String faultString = sfce.getFaultStringOrReason() + ";";
            result = faultString;
            throw sfce;
        } catch (Exception e) {
            result = e.getMessage();
            throw e;
        } finally {
            // 保存日志
            if (result instanceof String) {
                String resultString = (String) result;
                Map<String, String> faultMap = new HashMap<String, String>();
                faultMap.put("faultDetail", resultString);
                result = faultMap;
            }
        }
    }

    public static <T> T marshalSendAndReceiveExt(String uri, Object requestObj, T responseObj, String soapType,
            String soapAction, final Object header, Map<String, String> headerMap) {

        if (soapAction == null) {
            soapAction = "";
        }

        Date startTime = new Date();
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setClassesToBeBound(requestObj.getClass());

        Jaxb2Marshaller jaxb2UmMarshaller = new Jaxb2Marshaller();
        jaxb2UmMarshaller.setClassesToBeBound(responseObj.getClass());
        MessageFactory msgFactory = null;
        try {
            msgFactory = MessageFactory.newInstance(soapType);
        } catch (SOAPException e) {
            e.printStackTrace();
        }

        WebServiceTemplateEx webServiceTemplate = new WebServiceTemplateEx(jaxb2Marshaller, jaxb2UmMarshaller, null,
                headerMap);
        SaajSoapMessageFactoryEx newSoapMessageFactory = new SaajSoapMessageFactoryEx(msgFactory);
        webServiceTemplate.setMessageFactory(newSoapMessageFactory);

        webServiceTemplate.setDefaultUri(uri);

        final String finalSoapAction = soapAction;

        Object result = null;
        try {
            @SuppressWarnings("unchecked")
            T resultObje = (T) webServiceTemplate.marshalSendAndReceive(uri, requestObj,
                    new WebServiceMessageCallback() {
                        @Override
                        public void doWithMessage(WebServiceMessage message) {

                            ((SoapMessage) message).setSoapAction(finalSoapAction);

                            SoapHeader soapHeader = ((SoapMessage) message).getSoapHeader();
                            // create a marshaller

                            Marshaller marshaller;
                            try {
                                assert header != null;
                                JAXBContext context = JAXBContext.newInstance(header.getClass());
                                marshaller = context.createMarshaller();
                                marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper",
                                        new NamespacePrefixMapper() {
                                            @Override
                                            public String getPreferredPrefix(String namespaceUri, String suggestion,
                                                    boolean requirePrefix) {
                                                if (namespaceUri.equals(wss4j_utility))
                                                    return "wsu";
                                                if (namespaceUri.equals(wss4j_secext))
                                                    return "wsse";
                                                return suggestion;
                                            }
                                        });
                                marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);// 是否省略xml头信息
                                marshaller.marshal(header, soapHeader.getResult());
                            } catch (JAXBException e) {
                                e.printStackTrace();
                            }
                        }
                    });
            result = resultObje;
            return resultObje;
        } catch (SoapFaultClientException sfce) {
            // QName faultCode = sfce.getFaultCode();
            String faultString = sfce.getFaultStringOrReason() + ";";
            result = faultString;
            throw sfce;
        } catch (Exception e) {
            result = e.getMessage();
            throw e;
        } finally {
            // 保存日志
            if (result instanceof String) {
                String resultString = (String) result;
                Map<String, String> faultMap = new HashMap<String, String>();
                faultMap.put("faultDetail", resultString);
                result = faultMap;
            }
        }
    }

    /**
     * webservice的soap请求
     *
     * @param uri 请求对方soap的URL,例如：http://yodel.myshippingsystem.com/api/abolapi.asmx
     * @param requestObj 求请参数的对象
     * @param responseObj 响应参数的对象，注意是对象。
     * @param soapType soap类型，例如SOAP 1.2 Protocol;
     * @param soapHeaderElements 可以为null
     * @param proxy 代理对像,可以为null
     * @param headerMap http头的信息，可以为null
     * @return T 响应参数的对象，与传入的响应对象responseObj不是同一个。
     * @throws SoapFaultClientException SoapFault的异常要把异常转成对像请调用com.fpx.channel.base.utils.XmlUtil.extractFaultDetail()
     */
    public static <T> T marshalSendAndReceive(String uri, Object requestObj, T responseObj, String soapType,
            List<SOAPHeaderElement> soapHeaderElements, Proxy proxy, Map<String, String> headerMap)
            throws SoapFaultClientException {
        Date startTime = new Date();
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setClassesToBeBound(requestObj.getClass());

        Jaxb2Marshaller jaxb2UmMarshaller = new Jaxb2Marshaller();
        jaxb2UmMarshaller.setClassesToBeBound(responseObj.getClass());
        MessageFactory msgFactory = null;
        SOAPMessage message = null;
        try {
            msgFactory = MessageFactory.newInstance(soapType);
            // 加入头部信息
            if (null != soapHeaderElements && !soapHeaderElements.isEmpty()) {
                message = msgFactory.createMessage();
                SOAPPart soapPart = message.getSOAPPart();
                SOAPEnvelope envelope = soapPart.getEnvelope();
                SOAPHeader header = envelope.getHeader();
                if (header == null) {
                    header = envelope.addHeader();
                }
                for (SOAPHeaderElement sOAPHeaderElement : soapHeaderElements) {
                    header.addChildElement(sOAPHeaderElement);
                }
            }

        } catch (SOAPException e1) {
            logger.error("MessageFactory 初始化出错", e1);
            throw new RuntimeException(e1);

        }

        WebServiceTemplateEx webServiceTemplate = new WebServiceTemplateEx(jaxb2Marshaller, jaxb2UmMarshaller, proxy,
                headerMap);
        SaajSoapMessageFactoryEx newSoapMessageFactory = new SaajSoapMessageFactoryEx(msgFactory);
        newSoapMessageFactory.setSoapMessage(message);
        webServiceTemplate.setMessageFactory(newSoapMessageFactory);

        webServiceTemplate.setDefaultUri(uri);
        Object result = null;
        try {
            @SuppressWarnings("unchecked")
            T resultObje = (T) webServiceTemplate.marshalSendAndReceive(uri, requestObj);
            result = resultObje;
            return resultObje;
        } catch (SoapFaultClientException sfce) {
            // QName faultCode = sfce.getFaultCode();
            String faultString = sfce.getFaultStringOrReason() + ";";
            result = faultString;
            throw sfce;
        } catch (Exception e) {
            result = e.getMessage();
            throw e;
        } finally {
            // 保存日志
            if (result instanceof String) {
                String resultString = (String) result;
                Map<String, String> faultMap = new HashMap<String, String>();
                faultMap.put("faultDetail", resultString);
                result = faultMap;
            }
        }
    }

    /**
     * 获取设置头部信息
     *
     * @param namespaceURI
     * @param localPart
     * @param prefix
     * @param soapType
     * @return
     * @throws SOAPException
     */
    public static SOAPHeaderElement getSOAPHeaderElement(String namespaceURI, String localPart, String prefix,
            String soapType) {
        try {
            MessageFactory msgFactory = MessageFactory.newInstance(soapType);
            SOAPMessage message = msgFactory.createMessage();
            SOAPPart soapPart = message.getSOAPPart();
            SOAPEnvelope envelope = soapPart.getEnvelope();
            SOAPHeader header = envelope.getHeader();
            if (header == null) {
                header = envelope.addHeader();
            }
            QName qName = new QName(namespaceURI, localPart, prefix);
            return header.addHeaderElement(qName);
        } catch (SOAPException e) {
            logger.error("MessageFactory 获取设置头部信息", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取wss4j的头部信息
     *
     * @param soapType
     * @param security
     * @return
     */
    public static SOAPHeaderElement getWss4jSOAPHeaderElement(String soapType, Security security) {
        SOAPHeaderElement soapHeaderElement = getSOAPHeaderElement(Security.NAMESPACE_WSSE, Security.LOCALPART,
                Security.PREFIX, soapType);
        try {
            SOAPElement usernameTokenElement = soapHeaderElement.addChildElement("UsernameToken", Security.PREFIX,
                    Security.NAMESPACE_WSSE);
            SOAPElement userNameElement = usernameTokenElement.addChildElement("Username", Security.PREFIX,
                    Security.NAMESPACE_WSSE);
            userNameElement.setValue(security.getUsername());
            SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", Security.PREFIX,
                    Security.NAMESPACE_WSSE);
            if (null != security.getPassword_type()) {
                passwordElement.addAttribute(new QName("Type"), security.getPassword_type());
            }
            passwordElement.setValue(security.getPassword());

            if (!StringUtils.isEmpty(security.getNonce())) {
                SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", Security.PREFIX,
                        Security.NAMESPACE_WSSE);
                nonceElement.setValue(security.getNonce());
            }

            if (!StringUtils.isEmpty(security.getCreated())) {
                SOAPElement createdElement = usernameTokenElement.addChildElement("Created", Security.PREFIX,
                        Security.NAMESPACE_WSSE);
                createdElement.setValue(security.getCreated());
            }
        } catch (Exception e) {
            logger.error("soapHeaderElement 获取设置头部信息", e);
            throw new RuntimeException(e);
        }
        return soapHeaderElement;

    }

    /**
     * 获取设置头部信息
     *
     * @param soapType
     * @return
     * @throws SOAPException
     */
    public static SOAPHeader getSOAPHeaderElement(String soapType) {
        try {
            MessageFactory msgFactory = MessageFactory.newInstance(soapType);
            SOAPMessage message = msgFactory.createMessage();
            SOAPPart soapPart = message.getSOAPPart();
            SOAPEnvelope envelope = soapPart.getEnvelope();
            SOAPHeader header = envelope.getHeader();
            if (header == null) {
                header = envelope.addHeader();
            }
            return header;
        } catch (SOAPException e) {
            logger.error("MessageFactory 获取设置头部信息", e);
            throw new RuntimeException(e);
        }
    }
}
